Unstructured Grid Visualization Cookbook
Overview
Unstructured Grids are a powerful way to represent Geoscience data. Unlike the more commonly used Structured Grids, Unstructured Grids are composed of flexible geomentries, which allows for a customizable discretization of the Earth with a variable resolution. This makes them incredibly useful for filling in irregularly shaped domains like Earth’s oceans, or for achieving high resolutions in localized regions. However, working and analyzing Unstructured Grids requires additional overhead, since the mesh is represented as a set of points, edges, and faces. In this notebook, we will cover the following:
MPAS Ocean Mesh Dataset
Working with Unstructured Grids using UXarray
Plotting with HoloViz
Plotting with Matplotlib
Prerequisites
This section was inspired by this template of the wonderful The Turing Way Jupyter Book.
Following your overview, tell your reader what concepts, packages, or other background information they’ll need before learning your material. Tie this explicitly with links to other pages here in Foundations or to relevant external resources. Remove this body text, then populate the Markdown table, denoted in this cell with | vertical brackets, below, and fill out the information following. In this table, lay out prerequisite concepts by explicitly linking to other Foundations material or external resources, or describe generally helpful concepts.
Label the importance of each concept explicitly as helpful/necessary.
Concepts |
Importance |
Notes |
|---|---|---|
Necessary |
||
Helpful |
Familiarity with metadata structure |
|
Project management |
Helpful |
Time to learn: estimate in minutes. For a rough idea, use 5 mins per subsection, 10 if longer; add these up for a total. Safer to round up and overestimate.
System requirements:
Populate with any system, version, or non-Python software requirements if necessary
Otherwise use the concepts table above and the Imports section below to describe required packages as necessary
If no extra requirements, remove the System requirements point altogether
Imports
# Organization and data handling
import numpy as np
import pandas as pd
import uxarray as ux
import xarray as xr
import time
import requests
# General Plotting
import cartopy.crs as ccrs
import cartopy.feature as cfeature
# Plotting with matplotlib
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
# Plotting with datashader
import holoviews as hv
import hvplot.pandas
import geoviews.feature as gf
import warnings
warnings.filterwarnings("ignore")
# Create blue color theme
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("Color_Theme", plt.cm.Blues(np.linspace(0.2, 1, 30)))
MPAS Ocean Dataset
# Load data files from web
ds_file_480 = "../meshfiles/oQU480.230422.nc"
ds_file_120 = "../meshfiles/oQU120.230424.nc"
# Open datasets from files
ds_480 = xr.open_dataset(ds_file_480)
ds_120 = xr.open_dataset(ds_file_120)
ds_480
<xarray.Dataset>
Dimensions: (nEdges: 5754, maxEdges2: 12, TWO: 2,
nVertices: 3947, vertexDegree: 3, nCells: 1791,
nVertLevels: 60, maxEdges: 6, Time: 1)
Dimensions without coordinates: nEdges, maxEdges2, TWO, nVertices,
vertexDegree, nCells, nVertLevels, maxEdges,
Time
Data variables: (12/49)
edgesOnEdge (nEdges, maxEdges2) int32 ...
weightsOnEdge (nEdges, maxEdges2) float64 ...
cellsOnEdge (nEdges, TWO) int32 ...
verticesOnEdge (nEdges, TWO) int32 ...
angleEdge (nEdges) float64 ...
dcEdge (nEdges) float64 ...
... ...
refTopDepth (nVertLevels) float64 ...
refZMid (nVertLevels) float64 ...
refLayerThickness (nVertLevels) float64 ...
layerThickness (Time, nCells, nVertLevels) float64 ...
ssh (Time, nCells) float64 ...
zMid (Time, nCells, nVertLevels) float64 ...
Attributes: (12/14)
model_name: mpas
core_name: ocean
source: MPAS
Conventions: MPAS
git_version: v4.0-630-gaa1be43
on_a_sphere: YES
... ...
x_period: 0.0
y_period: 0.0
history: Sat Apr 22 16:39:35 2023: /gpfs/fs1/home/ac.xylar/chrysal...
parent_id: lsd2k42w1r\na4x6sqhkwb
mesh_spec: 0.0
file_id: kufh9o2jwxds_120
<xarray.Dataset>
Dimensions: (nEdges: 87980, maxEdges2: 12, TWO: 2,
nVertices: 59329, vertexDegree: 3,
nCells: 28571, nVertLevels: 100, maxEdges: 6,
Time: 1)
Dimensions without coordinates: nEdges, maxEdges2, TWO, nVertices,
vertexDegree, nCells, nVertLevels, maxEdges,
Time
Data variables: (12/49)
edgesOnEdge (nEdges, maxEdges2) int32 ...
weightsOnEdge (nEdges, maxEdges2) float64 ...
cellsOnEdge (nEdges, TWO) int32 ...
verticesOnEdge (nEdges, TWO) int32 ...
angleEdge (nEdges) float64 ...
dcEdge (nEdges) float64 ...
... ...
refTopDepth (nVertLevels) float64 ...
refZMid (nVertLevels) float64 ...
refLayerThickness (nVertLevels) float64 ...
layerThickness (Time, nCells, nVertLevels) float64 ...
ssh (Time, nCells) float64 ...
zMid (Time, nCells, nVertLevels) float64 ...
Attributes: (12/15)
model_name: mpas
core_name: ocean
source: MPAS
Conventions: MPAS
git_version: v4.0-973-g678b0d3
on_a_sphere: YES
... ...
y_period: 0.0
history: Mon Apr 24 15:11:05 2023: /gpfs/fs1/home/ac.xylar/chrysal...
parent_id: a3ix7sajjh\nfo1xwy6fbp\ne513bmrqm3\n
mesh_spec: 0.0
file_id: f5z41zseqm
NCO: 4.0.5Working with Unstructured Grids using UXarray
Loading Datasets
uxds_480 = ux.open_dataset(ds_file_480, ds_file_480)
uxds_480.uxgrid
<uxarray.Grid>
Original Grid Type: mpas
Grid Dimensions:
* nMesh2_node: 3947
* nMesh2_face: 1791
* nMaxMesh2_face_nodes: 6
* nMesh2_edge: 5754
Grid Coordinate Variables:
* Mesh2_node_x: (3947,)
* Mesh2_node_y: (3947,)
* Mesh2_face_x: (1791,)
* Mesh2_face_y: (1791,)
Grid Connectivity Variables:
* Mesh2_face_nodes: (1791, 6)
* Mesh2_edge_nodes: (5754, 2)
* nNodes_per_face: (1791,)
uxds_120 = ux.open_dataset(ds_file_120, ds_file_120)
uxds_120.uxgrid
<uxarray.Grid>
Original Grid Type: mpas
Grid Dimensions:
* nMesh2_node: 59329
* nMesh2_face: 28571
* nMaxMesh2_face_nodes: 6
* nMesh2_edge: 87980
Grid Coordinate Variables:
* Mesh2_node_x: (59329,)
* Mesh2_node_y: (59329,)
* Mesh2_face_x: (28571,)
* Mesh2_face_y: (28571,)
Grid Connectivity Variables:
* Mesh2_face_nodes: (28571, 6)
* Mesh2_edge_nodes: (87980, 2)
* nNodes_per_face: (28571,)
Plotting with HoloViz
Conversion to GeoDataFrame
gdf_480_grid = uxds_480.uxgrid.to_geodataframe()
gdf_120_grid = uxds_120.uxgrid.to_geodataframe()
Nodes
hv.extension("matplotlib")
plot_kwargs = {"size": 5.0, "xlabel":"Longitude", "ylabel": "Latitude", "xlim": (-110, -50), "ylim": (0, 40),
"coastline": True, "width": 800}
hv.Layout(gdf_480_grid.hvplot.points(**plot_kwargs) + gdf_120_grid.hvplot.points(**plot_kwargs)).opts(fig_size=300).cols(1)
hv.extension("bokeh")
plot_kwargs = {"s": 4.0, "xlabel":"Longitude", "ylabel": "Latitude", "xlim": (-110, -50), "ylim": (0, 40),
"coastline": True}
hv.Layout(gdf_480_grid.hvplot.points(**plot_kwargs) + gdf_120_grid.hvplot.points(**plot_kwargs)).cols(1)
Edges
hv.extension("matplotlib")
plot_kwargs = {"linewidth": 0.5, "xlabel":"Longitude", "ylabel": "Latitude", "xlim": (-110, -50), "ylim": (0, 40),
"coastline": True, "width": 1600}
hv.Layout(gdf_480_grid.hvplot.paths(**plot_kwargs) + gdf_120_grid.hvplot.paths(**plot_kwargs)).opts(fig_size=300).cols(1)
hv.extension("bokeh")
plot_kwargs = {"line_width": 0.5, "xlabel":"Longitude", "ylabel": "Latitude", "xlim": (-110, -50), "ylim": (0, 40),
"coastline": True}
hv.Layout(gdf_480_grid.hvplot.paths(**plot_kwargs) + gdf_120_grid.hvplot.paths(**plot_kwargs)).cols(1)
